package jmine.tec.environment.db.server;

import java.io.File;
import java.io.PrintWriter;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import jmine.tec.persist.schema.api.SqlDialect;
import jmine.tec.persist.schema.impl.dialects.HSQLDialect;

import org.apache.log4j.Logger;
import org.hsqldb.Server;
import org.hsqldb.ServerConstants;

import bancosys.tec.utils.db.converter.DbConverter;
import bancosys.tec.utils.db.converter.OracleToHsqlDbConverter;
import bancosys.tec.utils.db.executor.DbExecutor;
import bancosys.tec.utils.db.executor.HsqlDbExecutor;

/**
 * @author Rafael Volpato (May 11, 2007)
 * @author galmeida (Sep 24, 2008)
 */
public class HSQLTestDBServer extends AbstractTestDBServer implements ManagedTestDBServer {
    private static final Logger LOG = Logger.getLogger(HSQLTestDBServer.class);

    private static Map<String, Server> hsqlServerInstances = new HashMap<String, Server>();

    /**
     * Devolve a instância existente ou cria uma nova instância de servidor HSQL
     * 
     * @param connectionUrl url de conexão
     * @return uma instância de servidor HSQL
     */
    protected static Server getServerInstance(String connectionUrl) {
        if (hsqlServerInstances.get(connectionUrl) == null) {
            // Forcando unicode.
            System.setProperty("sqlfile.charset", "UTF-8");

            Server hsqlServerInstance = new Server();
            hsqlServerInstance.setLogWriter(null);
            hsqlServerInstance.setErrWriter(new PrintWriter(System.err));
            hsqlServerInstances.put(connectionUrl, hsqlServerInstance);
        }
        return hsqlServerInstances.get(connectionUrl);
    }

    /**
     * Construtor.
     */
    public HSQLTestDBServer() {
        super();
    }

    /**
     * Apaga os dados de todas as tabelas exceto as especificadas no <code>skipTables</code> (DELETE).
     * 
     * @param skipTables tabelas que não devem ser apagadas.
     */
    public void clearDBData(String... skipTables) {
        List<String> skipList = new LinkedList<String>();

        if (skipTables != null) {
            for (String element : skipTables) {
                skipList.add(element.toUpperCase());
            }
        }

        Connection conn = null;
        try {
            conn = this.getConnection();
            List<String> tables = this.getTableNames(conn);

            Statement st = conn.createStatement();

            /* Disable constraints check */
            st.executeUpdate("SET REFERENTIAL_INTEGRITY FALSE");

            /* Delete table data */
            for (String table : tables) {
                if (!skipList.contains(table.toUpperCase())) {
                    st.executeUpdate("DELETE FROM " + table);
                }
            }

            /* Enable constraints check */
            st.executeUpdate("SET REFERENTIAL_INTEGRITY TRUE");

        } catch (SQLException e) {
            throw new TestDBServerException(e);
        } finally {
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                throw new TestDBServerException(e);
            }
        }
    }

    /**
     * Apaga todas as tabelas (DROP).
     * 
     * @throws SQLException caso ocorra algum erro.
     */
    public void dropTables() throws SQLException {
        Connection conn = null;
        try {
            conn = this.getConnection();
            List<String> tables = this.getTableNames(conn);

            Statement st = conn.createStatement();

            Iterator<String> it = tables.iterator();
            while (it.hasNext()) {
                String table = it.next();
                st.executeUpdate("DROP TABLE " + table + " IF EXISTS CASCADE");
            }

        } finally {
            try {
                if (conn != null) {
                    conn.close();
                }
            } catch (SQLException e) {
                throw new TestDBServerException(e);
            }
        }
    }

    /**
     * Inicia o servidor com as seguintes propriedades:
     * <code>"database.0=mem:" + databaseName + ";dbname.0=" + databaseName + ";port=9002;sql.enforce_strict_size=true"</code>.
     */
    public void start() {
        /* Listen on a different port to avoid problems with the old persistence tests */
        if (!this.isRunning()) {
            String dbName = this.getDbName();
            if (dbName != null) {
                String url = this.dataSource.getUrl();
                String port = url.substring(url.lastIndexOf(':') + 1, url.lastIndexOf('/'));
                String serverProps = "database.1=mem:" + dbName + ";dbname.1=" + dbName + ";port=" + port + ";sql.enforce_strict_size=true";
                HSQLTestDBServer.getServerInstance(this.dataSource.getUrl()).putPropertiesFromString(serverProps);
                LOG.debug("starting server with props: " + serverProps);
            } else {
                LOG.debug("starting server without custom props.");
            }
            HSQLTestDBServer.getServerInstance(this.dataSource.getUrl()).start();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getDbName() {
        if (this.dataSource.getUrl().indexOf('/') > 0) {
            return super.getDbName();
        } else {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    public void stop() {
        HSQLTestDBServer.getServerInstance(this.dataSource.getUrl()).stop();
    }

    /**
     * {@inheritDoc}
     */
    public void save(String key) {
        // vazio
    }

    /**
     * {@inheritDoc}
     */
    public boolean restore(String key) {
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isRunning() {
        int state = HSQLTestDBServer.getServerInstance(this.dataSource.getUrl()).getState();
        return (state == ServerConstants.SERVER_STATE_ONLINE || state == ServerConstants.SERVER_STATE_OPENING);
    }

    /**
     * @return O DbExecutor que deve ser utilizado oara este DbServer
     */
    public DbExecutor getDbExecutor() {
        return new HsqlDbExecutor();
    }

    /**
     * @param inputFile O arquivo com o schema de criação do banco
     * @param outputFile O arquivo convertido
     * @param dbName O nome do banco
     * @return o DbConverter
     */
    public DbConverter getDbConverter(URL inputFile, File outputFile, String dbName) {
        return new OracleToHsqlDbConverter(inputFile, outputFile, dbName);
    }

    /**
     * {@inheritDoc}
     */
    public SqlDialect getDialect() {
        return new HSQLDialect();
    }

    /**
     * {@inheritDoc}
     */
    public void disableBatchMode(Connection conn) {
        // OK
    }

    /**
     * {@inheritDoc}
     */
    public void enableBatchMode(Connection conn) {
        // OK
    }
}
